/**
 * @Description: 对 uni.request 的封装处理
 * @description: 对外暴露 abortRequest、get、post、del(DELETE)、put、request(接受原始参数)
 * @description: 打印错误日志，对非success的请求自动 showToast，登录过期静默登录机制并存储请求队列到合适时重走流程
 * @author 刘贵
 * @date 2021/04/07
 */

import Vue from 'vue'
import axios from 'axios'
import { isPlainObject } from 'lodash'

const vm = Vue.prototype

let requestTasks = {}
let isRefreshTokenIng = false
const successCode = 0

// 记录和显示错误
function errorLog (error, notShowToast = false, response = null) {
  vm.$store.dispatch('ec/log/push', {
    message: '数据请求异常',
    type: 'danger',
    meta: {
      error
    }
  })

  setTimeout(() => {
    let msg
    if (response && response.data) {
      msg = response.data.message
    }
    if (!msg) {
      msg = error.message || error.msg || error.errMsg
    }

    if (!notShowToast && !isRefreshTokenIng && msg !== 'request:fail abort') {
      const aliasMessage = getErrorAliasMessage(msg)
      if (aliasMessage) {
        msg = aliasMessage
      }
      vm.$showToast(msg, 'error')
    }
  }, 200)
}

const errorAlias = {
  'Network Error': '网络错误',
  'request:fail': '网络连接超时，请稍后重试',
  'request:fail timeout': '网络连接超时，请稍后重试',
  'request:fail abort statusCode:-1': '网络连接超时，请稍后重试'
}

function complementedUrl (url) {
  return (!/^http/.test(url) ? vm.$configs.baseUrl : '') + url
}

function getErrorAliasMessage (msg) {
  if (/timeout of (\d)+ms exceeded/.test(msg)) {
    return '请求超时'
  } else {
    return errorAlias[msg] || msg
  }
}

/**
 * @description 组装获取请求配置
 * @param {String} method 请求类型
 * @param {Object} params 请求数据
 * @param {Object} data 请求数据
 * @param {String} url 请求url
 * @returns {{method: *, url: *}}
 */
function getRequestConfigs (method, { transformData = undefined, params = null, data = null, url = '', headers = {} } = {}) {
  const configs = {
    transformData,
    url,
    method: method,
    headers: isPlainObject(headers) ? headers : {}
  }
  if (data) {
    configs.data = data
  }
  if (params) {
    configs.params = params
  }
  return configs
}

/**
 * @description 请求出错处理
 * @param {Error} error 错误对象
 * @param {String} method 请求类型
 * @param {Function} request 请求执行函数
 * @param {Object} configs 配置
 * @param {Function} resolve 队列中的resolve回调
 * @param {Function} reject 队列中的reject回调
 * @param ags request执行函数的入参
 */
function errorHandler ({ error, method, request, configs, resolve, reject }, ags = []) {
  returnPromiseResult(resolve, reject, error, ags[0])
}

function returnPromiseResult (resolve, reject, data, alwaysReturnResolve) {
  if (alwaysReturnResolve) {
    resolve(data)
  } else {
    reject(data)
  }
}

function logRequestInfo (configs) {
  let subData = []
  const method = configs.method.toUpperCase()
  if (method !== 'GET') {
    if (configs.data) {
      subData.push('data')
      subData.push(configs.data)
    }
    if (configs.params) {
      subData.push('params')
      subData.push(configs.params)
    }
    if (!configs.data && !configs.params) {
      subData.push('无')
    }
    console.log('请求信息', method, configs.url)
    console.log('请求数据', ...subData)
  }
}

/**
 * @description 发起请求服务
 * @param configs 配置
 * @param abortSameRequest 如果已经有相同的请求，则取消上一个相同的请求，再开始新的请求发起
 * @param hideErrorToast 出现错误时，不再自动showToast
 * @returns {Promise<unknown>}
 */
const _service = axios.create({
  timeout: 40000
})

_service.interceptors.request.use(configs => {
  /* const accessToken = vm.$cookies.get('access_token')
  if (!configs.headers) { configs.headers = {} }
  if (accessToken) {
    configs.headers.Authorization = 'bearer ' + accessToken
  } */
  // TODO 备用头设置 application/x-www-form-urlencoded
  if (!/^http/.test(configs.url)) {
    configs.baseURL = vm.$configs.baseUrl
  }
  return configs
}, () => {
  // TODO 处理错误x
})

_service.interceptors.response.use(
  async response => {
    let resData = response.data

    // 自定义返回结果
    if (response.config && response.config.transformData) {
      return response.config.transformData(resData)
    }

    // 检测是否是下载文件
    for (const header in response.headers) {
      if (response.headers.hasOwnProperty(header) && header.toLowerCase() === 'content-disposition') {
        return {
          data: resData,
          filename: window.decodeURI(response.headers[header].split('filename=')[1])
        }
      }
    }

    // 如果是blob数据类型，处理返回数据以兼容服务器返回的错误报告
    if (hasBlob(response)) {
      const parseResult = await blobToJson(resData)
      // 如果解析成功 说明有返回的code等信息，赋值替换后走正常流程
      if (parseResult) {
        resData = parseResult
        response.data = parseResult
      } else {
        // 否则说明是blob数据 直接return
        return {
          code: successCode,
          data: resData
        }
      }
    }

    const { code } = resData
    const msg = resData.msg || resData.message
    // return refreshToken(resData.msg)
    // 如果是获取token 获取到access_token信息后直接返回 因为目前此接口不返回code
    if (resData.access_token && resData.refresh_token) {
      return resData
    }

    // 否则验证处理返回信心
    switch (code) {
      case successCode:
        // code === 0 代表没有错误
        return resData

      // 需要重新登录
      // 50008 - 无效的 token
      // 50012 - 其它客户端登录
      // 50014 - token 过期
      case 50008:
      case 50012:
      case 50014:
        // 其它和后台约定的 code
        /* Notification.error({
          title: '身份验证失败',
          message: '请重新登录'
        }) */
        console.log('身份验证失败，请执行退出登录')
        // await store.dispatch('ec/user/logout', { focus: true, remote: false, back: true })
        break
      default:
        // 不是正确的 code
        const err = {
          msg: (msg || ''),
          title: '非预期的Code',
          response: response
        }
        return Promise.reject(err)
    }
  },
  async error => {
    // 如果是blob数据类型，处理返回数据以兼容服务器返回的错误报告
    if (hasBlob(error.response)) {
      const parseResult = await blobToJson(error.response.data)
      if (parseResult) {
        error.response.data = parseResult
      }
    }

    let errorTitle = '错误'
    if (error && error.response) {
      switch (error.response.status) {
        case 400: errorTitle = '错误'; break
        case 401: errorTitle = '未授权，请登录'; break
        case 403: errorTitle = '拒绝访问'; break
        case 404: errorTitle = '请求地址出错'; break
        case 408: errorTitle = '请求超时'; break
        case 500: errorTitle = '服务器内部错误'; break
        case 501: errorTitle = '服务未实现'; break
        case 502: errorTitle = '网关错误'; break
        case 503: errorTitle = '服务不可用'; break
        case 504: errorTitle = '网关超时'; break
        case 505: errorTitle = 'HTTP版本不受支持'; break
        default: break
      }
    }
    error.title = errorTitle
    return Promise.reject(error)
  }
)

function hasBlob (response) {
  return response && response.request.responseType === 'blob'
}

function blobToJson (data) {
  return new Promise(resolve => {
    const fileReader = new FileReader()
    fileReader.onload = function () {
      try {
        let jsonData = JSON.parse(this.result)
        // 解析对象成功说明是普通数据
        resolve(jsonData)
      } catch (err) {
        // 解析成对象失败，说明是正常的文件流
        resolve(false)
      }
    }
    fileReader.readAsText(data)
  })
}

function service (configs = {}, abortSameRequest, hideErrorToast) {
  return new Promise((resolve, reject) => {
    configs.url = complementedUrl(configs.url)

    if (abortSameRequest) {
      abortRequest(configs.url)
    }

    const CancelToken = axios.CancelToken
    const source = CancelToken.source()
    requestTasks[configs.url] = source

    configs.cancelToken = source.token

    // TODO 打印要提交的数据
    logRequestInfo(configs)

    _service(configs)
      .then((response) => {
        resolve(response)
      })
      .catch(err => {
        reject(err)
        errorLog(err, hideErrorToast, err.response)
      })
      .finally(() => {
        if (requestTasks[configs.url]) {
          delete requestTasks[configs.url]
        }
      })
  })
}

/**
 * @description 取消一个请求
 * @param {String} url 要取消的请求url
 */
export function abortRequest (url) {
  url = complementedUrl(url)
  if (requestTasks[url] && requestTasks[url].cancel) {
    requestTasks[url].cancel('request:fail abort')
  }
}
/*
  alwaysReturnResolve 总是以resolve返回结果，用于 await 语句时 reject 返回的结果不用catch收不到的情况用
  abortSameRequest 设置为true时，如果有多个同样的url请求，后面的会把前面的还未完成的请求取消掉， 只识别url 不会去判断参数
  hideErrorToast 设置为true时，隐藏发生错误时的提示
 */

/**
 * @description 接受原始参数
 * @param configs 原始参数
 * @param {Boolean} alwaysReturnResolve
 * @param {Boolean} abortSameRequest
 * @param {Boolean} hideErrorToast
 * @returns {Promise<unknown>}
 */
export function request (configs, { alwaysReturnResolve = false, abortSameRequest = false, hideErrorToast = false } = {}) {
  return new Promise((resolve, reject) => {
    service(configs, abortSameRequest, hideErrorToast).then(resolve).catch(error => {
      errorHandler({ error, method: 'request', request: request, configs, resolve, reject }, [alwaysReturnResolve, abortSameRequest, hideErrorToast])
    })
  })
}

/**
 * @description 发起一个get请求
 * @param {String} url 原始参数
 * @param {Object} params 原始参数
 * @param {String} key 请求数据的key指定 params/data
 * @param {Boolean} alwaysReturnResolve
 * @param {Boolean} abortSameRequest
 * @param {Boolean} hideErrorToast
 * @returns {Promise<unknown>}
 */
export function get (url, params = null, { key = 'params', transformData, headers = {}, alwaysReturnResolve = false, abortSameRequest = false, hideErrorToast = false } = {}) {
  const configs = { [key]: params, url, transformData, headers }
  const mergeConfigs = getRequestConfigs('GET', configs)
  return new Promise((resolve, reject) => {
    service(mergeConfigs, abortSameRequest, hideErrorToast).then(resolve).catch(error => {
      errorHandler({ error, method: 'get', request: get, configs, resolve, reject }, [alwaysReturnResolve, abortSameRequest, hideErrorToast])
    })
  })
}

/**
 * @description 发起一个post请求
 * @param {String} url 原始参数
 * @param {Object} data 原始参数
 * @param {String} key 请求数据的key指定 params/data
 * @param {Boolean} alwaysReturnResolve
 * @param {Boolean} abortSameRequest
 * @param {Boolean} hideErrorToast
 * @returns {Promise<unknown>}
 */
export function post (url, data = null, { key = 'data', transformData, headers = {}, alwaysReturnResolve = false, abortSameRequest = false, hideErrorToast = false } = {}) {
  const configs = { [key]: data, transformData, url, headers }
  const mergeConfigs = getRequestConfigs('POST', configs)
  return new Promise((resolve, reject) => {
    service(mergeConfigs, abortSameRequest, hideErrorToast).then(resolve).catch(error => {
      errorHandler({ error, method: 'post', request: post, configs, resolve, reject }, [alwaysReturnResolve, abortSameRequest, hideErrorToast])
    })
  })
}

/**
 * @description 发起一个put请求
 * @param {String} url 原始参数
 * @param {Object} data 原始参数
 * @param {String} key 请求数据的key指定 params/data
 * @param {Boolean} alwaysReturnResolve
 * @param {Boolean} abortSameRequest
 * @param {Boolean} hideErrorToast
 * @returns {Promise<unknown>}
 */
export function put (url, data = null, { key = 'data', transformData, headers = {}, alwaysReturnResolve = false, abortSameRequest = false, hideErrorToast = false } = {}) {
  const configs = getRequestConfigs('PUT', { [key]: data, url, transformData, headers })
  return new Promise((resolve, reject) => {
    service(configs, abortSameRequest, hideErrorToast).then(resolve).catch(error => {
      errorHandler({ error, method: 'put', request: put, configs, resolve, reject }, [alwaysReturnResolve, abortSameRequest, hideErrorToast])
    })
  })
}

/**
 * @description 发起一个delete请求
 * @param {String} url 原始参数
 * @param {Object} params 原始参数
 * @param {String} key 请求数据的key指定 params/data
 * @param {Boolean} alwaysReturnResolve
 * @param {Boolean} abortSameRequest
 * @param {Boolean} hideErrorToast
 * @returns {Promise<unknown>}
 */
export function del (url, params = null, { key = 'params', transformData, headers = {}, alwaysReturnResolve = false, abortSameRequest = false, hideErrorToast = false } = {}) {
  const configs = getRequestConfigs('DELETE', { [key]: params, url, transformData, headers })
  return new Promise((resolve, reject) => {
    service(configs, abortSameRequest, hideErrorToast).then(resolve).catch(error => {
      errorHandler({ error, method: 'delete', request: del, configs, resolve, reject }, [alwaysReturnResolve, abortSameRequest, hideErrorToast])
    })
  })
}
